PHASE5: Build contraction based HighOrder2 graph

PHASE5: Build contraction based HighOrder2 graph

Summary
Based on contraction node/edge graph from PHASE4, this phase creates new contracted_edges of to-be graph. Then identifies, nearby contracted_edges and link them into contracted_edge_links. Finally -- using contracted_edge and contracted_edge_links, creates a new HighOrder2 graph

STEP1: Clean up any old scratch works
TRUNCATE TABLE contracted_edge;

STEP2: Adding shortcuts with more than 5 Contraction Depths
SELECT * FROM activity_log WHERE title='GenerateGraph: Completed' ORDER BY at DESC limit 10;
<<"maxEdgeId":"12337862395">>
NOTE:
maxEdgeId is largest ID used by generate-graph stage and hence first_edge_id after that will be for contracted edges

SET statement_timeout=3600000;
\set first_edge_id 12337862395

SELECT contraction_depth,COUNT(edge_id) FROM edge WHERE edge_id > :first_edge_id AND contraction_depth <> 0 GROUP BY contraction_depth ORDER BY contraction_depth DESC;
<<Runs for ~15min>>

Contraction Depth Histogram:
contraction_depth | count
-------------------+------------
20 | 3
19 | 1
18 | 4
17 | 17
16 | 48
15 | 119
14 | 397
13 | 889
12 | 1792
11 | 4240
10 | 10796
9 | 37276
8 | 186388
7 | 709624
6 | 2415205
5 | 8723003
4 | 39835646
3 | 207462594
2 | 978456218
1 | 3299378722

NOTE:
Total edges with >= 4 edges" is: 51925448; >=5 is: 12089802

SELECT * FROM activity_log WHERE title='GenerateGraph: Completed' ORDER BY at DESC limit 10;
<<"maxEdgeId":"12337862395"; "maxEdgeId":"933119931">>
NOTE:
maxEdgeId is largest ID used by generate-graph stage and hence first_edge_id after that will be for contracted edges

SET statement_timeout=9600000;

SELECT 4 AS min_contraction_depth, 10000000 AS batch_size, 12337862395 AS first_edge_id \gset

TRUNCATE TABLE contracted_edge;
CALL g2k_extract_contracted_edges(:min_contraction_depth, :batch_size, :first_edge_id);
NOTE:
Runs for 1hr

SELECT COUNT(*) FROM contracted_edge;
<<Returns: 51925448>>

SELECT contraction_depth,COUNT(edge_id) FROM contracted_edge GROUP BY contraction_depth ORDER BY contraction_depth DESC;
contraction_depth | count
-------------------+----------
20 | 3
19 | 1
18 | 4
17 | 17
16 | 48
15 | 119
14 | 397
13 | 889
12 | 1792
11 | 4240
10 | 10796
9 | 37276
8 | 186388
7 | 709624
6 | 2415205
5 | 8723003
4 | 39835646

STEP3: Populating shortcut geometries
This step will expand contracted edges by underlying uncontracted edges.
Also, it extracts edgeIds/nodeIds and point/line lat/lon to the table: contracted_edge

nohup ${PCH_APP_HOME}/run_G2KContractedEdgeBuilder.sh --action buildContractedEdges --fixIssues true --rowBeginAt=0 --rowEndAt=51930000 > $PCH_LOG_HOME/nohup.out 2>&1 &
NOTE:
fixIssues is set to true to update (if found incorrect) edge's uncotracted-edge dual-list
simple_cost_spec_by_db.json is required for CostBasedGridGraphGenerator that is internally used by G2KContractedEdgeBuilder
rowBeginAt, and rowEndAt are used to batch the processing (good to keep them in multiples of fetchBatchSize in JSON)
rowEndAt should eventually excede to total rows in contracted_edge
Takes 8hrs -- with 140 seconds per thread of every 5000 rows in contracted_edge table to fix and update (45 seconds for update-only)

STEP5a: Populating geom and distance of shortcuts
SELECT COUNT(*) FROM contracted_edge;
<<Returns: 44735738>>

SET statement_timeout=9600000; SET lock_timeout=1200000; SET work_mem TO '512MB';
SET statement_timeout=1800000; SET work_mem TO '256MB';
CALL g2k_update_contracted_edge_distance(0, 20);
CALL g2k_update_contracted_edge_distance(20, 40);
CALL g2k_update_contracted_edge_distance(40, 60);
CALL g2k_update_contracted_edge_distance(60, 80);
CALL g2k_update_contracted_edge_distance(80, 100);

SELECT MIN(distance), MAX(distance), AVG(distance) FROM contracted_edge;
min | max | avg
-----+---------------------+-----------------------
0 | 0.00714664501706132 | 0.0019149804815543686
NOTE: longest edge is 0.71km with average of 0.191km

Result of contracted_edges are the long paths all across the graph:
g2k_contracted_shortcut_links_zoomed.png
Ref: C:\GangaToKaveri\docs\g2k_contracted_shortcut_links_zoomed.png

STEP7: Populating nearest_contracted_edges by touching and non-touching edges
NOTE: nearest_contracted_edges is used by "Linking neighborhood shortcuts" tasks

Connect to database container
SET statement_timeout=1800000;

SELECT 4 AS NEAREST_SHORTCUTS_TO_LINK, 2 AS MAX_ZERO_DISTANCE_SHORTCUTS_TO_LINK \gset

CREATE UNLOGGED TABLE IF NOT EXISTS nearest_contracted_edges_tmp (
src_contracted_edge_id bigint NOT NULL,
dst_contracted_edge_id bigint NOT NULL,
distance double precision NOT NULL
);
ALTER TABLE nearest_contracted_edges_tmp ADD CONSTRAINT nearcntrctdedgetukey UNIQUE(src_contracted_edge_id,dst_contracted_edge_id);

nohup ${PCH_APP_HOME}/run_G2KContractedEdgeLinker.sh --action runBuildNearestContractedEdges > $PCH_LOG_HOME/nohup.out 2>&1 &

NOTE:
Runs in executorThreads count of concurrent threads of batches of buildNearestContractedEdgeBatchRows
executorBusyTillThreadsRunning allows the task to re-feed the concurrent executor with next batch of tasks if busy threads falls below this number
Runs for ~300min -- creating 178899996 entries
runBuildNearestContractedEdges activities are 100% done in database using light Java processes. Hence you can colocate java container in database server.

  "graphDBConnectionInitSql": "SET statement_timeout = 4800000; SET lock_timeout=2400000;",
  "graphDBConnectionInitSql": "SET statement_timeout = 4800000; SET lock_timeout=2400000;",

SELECT COUNT(*) FROM nearest_contracted_edges_tmp;

    On PROD:
      count
    ---------
    178899996
    Returns:
      count
    ---------
    178899996

Verify (count should be zero) that all src edges have atleast one dst edge not touching each other:
SELECT COUNT(DISTINCT src_contracted_edge_id) FROM nearest_contracted_edges_tmp
WHERE 1=1
AND distance = 0
AND src_contracted_edge_id NOT IN (
SELECT DISTINCT src_contracted_edge_id FROM nearest_contracted_edges_tmp
WHERE distance > 0);
<<count should be zero>>

TRUNCATE TABLE nearest_contracted_edges;

\set NEAREST_SHORTCUTS_TO_LINK 4

INSERT INTO nearest_contracted_edges (src_contracted_edge_id, dst_contracted_edge_id, distance)
SELECT src_contracted_edge_id,dst_contracted_edge_id,distance FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY src_contracted_edge_id ORDER BY distance) AS row_by_dist, nearest.*
FROM nearest_contracted_edges_tmp AS nearest) AS row_counted_nearest
WHERE row_counted_nearest.row_by_dist <= :NEAREST_SHORTCUTS_TO_LINK;
<<INSERT 0 178899996>>

NOTE:
Runs for over 100sec -- creating 178899996 entries
Getting six/five nearest shortcuts to each shortcut (distance=0 means overlapping)

DROP TABLE nearest_contracted_edges_tmp;
SELECT MIN(distance), MAX(distance), AVG(distance) FROM nearest_contracted_edges;
min | max | avg
------------------------+---------------------+-----------------------
0.00027777777776805124 | 0.07111165364376422 | 0.0016966237697779352
NOTE: farthest contracted-edge to be linked is 0.71km; average is 0.17km

Remove abnormal nearest links:
SELECT * FROM nearest_contracted_edges WHERE distance > 0.06;
id | src_contracted_edge_id | dst_contracted_edge_id | distance
-----------+------------------------+------------------------+-------------------
78247019 | 14475201694 | 14599649265 | 0.060123789
78247020 | 14475201694 | 16669695116 | 0.064834553
78247021 | 14475201694 | 16669695117 | 0.065432553
78247022 | 14475201694 | 14600566211 | 0.066432553
210051595 | 16345482682 | 14599649265 | 0.06783455
210051596 | 16345482682 | 16669695116 | 0.07000165
210051597 | 16345482682 | 16669695117 | 0.07110065
210051598 | 16345482682 | 14600566211 | 0.07111165

DELETE FROM nearest_contracted_edges WHERE id IN (78247019,78247020,78247021,78247022,210051595,210051596,210051597,210051598);

SELECT MIN(distance), MAX(distance), AVG(distance) FROM nearest_contracted_edges;
min | max | avg
------------------------+---------------------+-----------------------
0.00027777777776805124 | 0.01503339903080579 | 0.0016624573080090011

NOTE: farthest contracted-edge to be linked is 1.5km; but average distance is 0.16km

STEP9a: Linking neighborhood shortcuts (populating the table: contracted_edge_links)

NOTE:
nearestShortcutsToLink is selected 4 for DEV and 5 for PROD -- as higher value creates unmanageable super-mega clusters
nearestShortcutsToLink should be what was used as NEAREST_SHORTCUTS_TO_LINK in script while populating nearest_contracted_edges
secondaryCacheNodeSize can be large for "FORWARD" only search and hence is 6291456 for DEV on 32GB 9437184 on 64GB; 14680064 for PROD on 128GB
Shortcut caches are disabled since shortcuts should be calculated fresh.
For build action, fetchBatchSize should be multiple of nearestShortcutsToLink
batchedNodeAccessBatchSlots is set to -1 to disable batched MapData access -- as it is slow for light-weight forward-only
executorThreads is set to 7 for DEV on 8 core system; 40 on 48 core PROD system
On PROD, graphDBPoolSize is set to 35 (from 30) in prod to support 32 threads (PostGreSQL's max_connections=40)
maxNeighborCost thru solutionToEdgeRatioProfitableLimits are increased from contraction's values to make Dijkstra to search deeper and wider
skipNearestShortcutsToLinkNodesPct decides how many shortest paths to nearest edges are included. They are skipped if number of nodes in paths is < 50% to each other

nohup ${PCH_APP_HOME}/run_G2KContractedEdgeLinker.sh --action buildContractedEdgeLinks > $PCH_LOG_HOME/nohup.out 2>&1 &
NOTE:
The script will run for (TOTAL_CONTRACTED_EDGES*NEAREST_SHORTCUTS_TO_LINK/TOTAL_CONTRACTED_EDGES_PER_BATCH)+1 times
Each batch of 900000 rows run for ~70min/batch and completes all batches in ~5 days

STEP9b: Populating shortcut-link distances
SET statement_timeout=9600000;

CALL g2k_update_contracted_edge_links_distance(0, 20);
CALL g2k_update_contracted_edge_links_distance(20, 40);
CALL g2k_update_contracted_edge_links_distance(40, 60);
CALL g2k_update_contracted_edge_links_distance(60, 80);
CALL g2k_update_contracted_edge_links_distance(80, 101);

NOTE:
It runs for 10min
You can parallelize using multiple sessions

SELECT COUNT(*) FROM contracted_edge_links;
On PROD:
count
---------
72984124

SELECT MIN(distance), MAX(distance), AVG(distance) FROM contracted_edge_links;

          min           |         max         |         avg
------------------------+---------------------+----------------------
 0.00027777777776805124 | 0.09849835194712261 | 0.001632588301654219
          min           |         max         |         avg
------------------------+---------------------+----------------------
 0.00027777777776805124 | 0.09849835194712261 | 0.001632588301654219

NOTE: longest edge on PROD is 9.8km and average of 0.16km

Result of contracted_edge, contracted_edge_links are the longer paths all across the graph:
g2k_contracted_shortcut_edges_linked_highlighted.png
Ref: C:\GangaToKaveri\docs\g2k_contracted_shortcut_edges_linked_highlighted.png

~~~~~ Take incremental DB backup ~~~~~

STEP10a: Build 2nd level nodes/edges based on contracted_edge, contracted_edge_links

NOTE: This high-order graph will be used to traverse through to identify intersecting contracted_edge, and contracted_edge_links to build clusters

SELECT 1 AS contracted_type, 2 AS contracted_link_type \gset

INSERT INTO edge_highorder2_staging (edge_id, from_node_id, to_node_id, edge_cost, access_only, contraction_depth, edge_type_code, ref_from_node_id, ref_to_node_id, ref_edge_id)
(
SELECT nextval('edge_highorder2_seq'), from_node_id, to_node_id, edge_cost, false, 0, :contracted_type, from_node_id, to_node_id, edge_id FROM contracted_edge
);

INSERT INTO edge_highorder2_staging (edge_id, from_node_id, to_node_id, edge_cost, access_only, contraction_depth, edge_type_code, ref_from_node_id, ref_to_node_id, ref_edge_id)
(
SELECT nextval('edge_highorder2_seq'), from_node_id, to_node_id, edge_cost, false, 0, :contracted_link_type, from_node_id, to_node_id, link_edge_id FROM contracted_edge_links
);

Build highorder2 edges/nodes:
UPDATE edge_highorder2_staging SET source_data_edge_id=edge_id;

TRUNCATE TABLE edge_highorder2;
TRUNCATE TABLE edge_ref_highorder2;

INSERT INTO edge_highorder2(edge_id, source_data_edge_id, from_node_id, to_node_id, edge_cost, access_only, contraction_depth, first_edge_id, second_edge_id)
SELECT edge_id, source_data_edge_id, from_node_id, to_node_id, edge_cost, access_only, contraction_depth, -1, -1 FROM edge_highorder2_staging;

ALTER SEQUENCE node_highorder2_seq RESTART WITH 1;
TRUNCATE TABLE node_highorder2_staging;

INSERT INTO node_highorder2_staging (node_id,ref_node_id,barrier,contraction_allowed,contraction_order,edges_from_ids,edges_to_ids)
WITH forward_edges AS (
SELECT from_node_id, array_agg(edge_id) AS edges_from_ids FROM edge_highorder2 GROUP BY from_node_id
), backward_edges AS (
SELECT to_node_id, array_agg(edge_id) AS edges_to_ids FROM edge_highorder2 GROUP BY to_node_id
)
SELECT nextval('node_highorder2_seq'), CASE WHEN forward_edges.from_node_id IS NOT NULL THEN forward_edges.from_node_id ELSE backward_edges.to_node_id END,
false, true, 9223372036854775807, forward_edges.edges_from_ids, backward_edges.edges_to_ids
FROM forward_edges FULL OUTER JOIN backward_edges ON forward_edges.from_node_id = backward_edges.to_node_id;

UPDATE node_highorder2_staging SET source_data_node_id=node_id;

TRUNCATE TABLE node_highorder2;
TRUNCATE TABLE node_ref_highorder2;

INSERT INTO node_highorder2 (node_id,source_data_node_id,barrier,contraction_allowed,contraction_order,edges_from_ids,edges_to_ids)
SELECT node_id,source_data_node_id,barrier,contraction_allowed,contraction_order,edges_from_ids,edges_to_ids FROM node_highorder2_staging;

INSERT INTO node_ref_highorder2 (node_id,ref_node_id)
SELECT node_id,ref_node_id FROM node_highorder2_staging;

DROP TABLE node_highorder2_staging;
DROP TABLE edge_highorder2_staging;

STEP10b: Update geometries of 2nd level reference nodes
This step will update ref_node_pt column in node_ref_highorder2 table

nohup ${PCH_APP_HOME}/run_G2KContractedEdgeBuilder.sh --action updateNodeRefGeometriesHighOrder2 > $PCH_LOG_HOME/nohup.out 2>&1 &

NOTE:
The step runs for 22 mins -- updating 58871942 rows

Check Graph Reachability:
nohup ${PCH_APP_HOME}/run_G2KGraphAnalyzer.sh --action graphAnalyzerCheckNodeReachabilityHighOrder2 --randomSeed 12345 > $PCH_LOG_HOME/nohup.out 2>&1 &

This will pick random nodes and reach all other nodes in forward and backward directions.
WARNING: Most of random nodes are not able to cover entire graph (fully reachable to everyone)
[G2KGraphAnalyzer] checkGraphReachability: Total forward nodes that could reach all nodes: 0 maxReachablePct: 0.042% minReachablePct: 0.000% out of 10 tries
[G2KGraphAnalyzer] checkGraphReachability: Total backward nodes that could reach all nodes: 0 maxReachablePct: ***0.013% ***minReachablePct: 0.000% out of 10 tries
[G2KGraphAnalyzer] checkGraphReachability: Total nodes that could reach all nodes: 0 maxReachablePct: 0.013% minReachablePct: 0.000% out of 10 tries

NOTE: With only contracted_edge and contracted_edge_links, the reachability is 0.04% or 0.13%:

STEP10c: Populating distances of 2nd level edges
SET statement_timeout=3600000;
CALL g2k_update_edge_distance_highorder2(0, 20);
CALL g2k_update_edge_distance_highorder2(20, 40);
CALL g2k_update_edge_distance_highorder2(40, 60);
CALL g2k_update_edge_distance_highorder2(60, 80);
CALL g2k_update_edge_distance_highorder2(80, 100);

SELECT MIN(distance), MAX(distance), AVG(distance) FROM edge_highorder2;
min | max | avg
-----+-------------------+-----------------------
0 | 0.578004235214351 | 0.0017126531160243977

SELECT COUNT(*) FROM edge_highorder2 WHERE distance > 0.05;

Returns:
count
-------
    85
Returns:
count
-------
    85

NOTE: Only 85/117719862 are more than 5km long

SELECT edge_id, distance FROM edge_highorder2 ORDER BY distance DESC LIMIT 10;

Returns:
  edge_id  |      distance
-----------+---------------------
  51373257 |   0.578004235214351
  51373254 |  0.5754844941555042
  51373250 |  0.5754844941555042
  51373252 |  0.5754844941555042
  50987727 | 0.26509906515751375
  50987715 | 0.26509906515751375
 112831236 |  0.2081906160391441
 112772645 | 0.20659693195276527
 112772643 | 0.20659693195276527
  91525218 | 0.17783022004181545
Returns:
  edge_id  |      distance
-----------+---------------------
  51373257 |   0.578004235214351
  51373254 |  0.5754844941555042
  51373250 |  0.5754844941555042
  51373252 |  0.5754844941555042
  50987727 | 0.26509906515751375
  50987715 | 0.26509906515751375
 112831236 |  0.2081906160391441
 112772645 | 0.20659693195276527
 112772643 | 0.20659693195276527
  91525218 | 0.17783022004181545

NOTE: Only few are very long

~~~~~ Take FULL DB backup ~~~~~